/* Copyright (c) 2007  Peter Palotas
 *  
 *  This software is provided 'as-is', without any express or implied
 *  warranty. In no event will the authors be held liable for any damages
 *  arising from the use of this software.
 *  
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *  
 *      1. The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software
 *      in a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  
 *      2. Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  
 *      3. This notice may not be removed or altered from any source
 *      distribution.
 *  
 *  
 *  $Id$
 */

using System;
using SCG = System.Collections.Generic;
using System.Text;
using C5;
using System.Security.Permissions;
using System.Runtime.InteropServices;
using System.ComponentModel;
using System.IO;

namespace Plossum.Win32.SystemServices
{
    /// <summary>
    /// Static class providing utility methods for working with Microsoft Windows devices and volumes. Most
    /// of the methods in this class is simple convenience methods for native Win32 API-calls to make them 
    /// simpler to use from managed languages.
    /// </summary>
    public static class VolumeManager
    {
        /// <summary>
        /// Defines or redefines MS-DOS device names.
        /// </summary>
        /// <param name="targetPath">A MS-DOS path string that will implement this device.</param>
        /// <param name="deviceName">An MS-DOS device name string specifying the device the function is 
        /// defining or redefining. The device name string must not have a trailing colon, unless a drive 
        /// letter (C or D, for example) is being defined or redefined. In no case is a trailing backslash allowed.</param>
        /// <returns><c>true</c> upon success, or <c>false</c> otherwise.</returns>
        /// <remarks>Call Marshal.GetLastWin32Error() to receive additional error information if this method fails.</remarks>
        public static bool DefineDosDevice(string targetPath, string deviceName)
        {
            return UnsafeNativeMethods.DefineDosDeviceW(0, deviceName, targetPath);
        }

        /// <summary>
        /// Deletes an MS-DOS device name.
        /// </summary>
        /// <param name="deviceName">An MS-DOS device name string specifying the device the function is 
        /// deleting. The device name string must not have a trailing colon, unless a drive 
        /// letter (C or D, for example) is being deleted. In no case is a trailing backslash allowed.</param>
        /// <returns><c>true</c> upon success, or <c>false</c> otherwise.</returns>
        /// <remarks>Call Marshal.GetLastWin32Error() to receive additional error information if this method fails.</remarks>
        public static bool DeleteDosDevice(string deviceName)
        {
            return UnsafeNativeMethods.DefineDosDeviceW(UnsafeNativeMethods.DDD_REMOVE_DEFINITION, deviceName, null);
        }

        /// <summary>
        /// Returns a sorted collection containing the names of the logical drives defined on the current computer.
        /// </summary>
        /// <returns>a sorted collection containing the names of the logical drives defined on the current computer.</returns>        
        /// <remarks>This function returns a concatenation of the logical disks in the Global and Local MS-DOS Device 
        /// namespaces. If a logical disk exists in both namespaces, this function will return the entry in the 
        /// Local MS-DOS Devices namespace.</remarks>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        public static ISorted<char> GetLogicalDrives()
        {
            uint r = UnsafeNativeMethods.GetLogicalDrives();
            
            ISorted<char> drives = new TreeSet<char>();

            for (char drive = 'A'; drive <= 'Z'; ++drive, r >>= 1)
            {
                if ((r & 1) != 0)
                    drives.Add(drive);
            }
            return drives;

        }

        /// <summary>
        /// Retreives a sorted collection containing the names of the logical drives currently NOT defined on the
        /// current computer.
        /// </summary>
        /// <returns>A sorted collection containing the names of the logical drives currently NOT defined on the
        /// current computer.</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate")]
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static ISorted<char> GetUnusedLogicalDrives()
        {
            uint r = UnsafeNativeMethods.GetLogicalDrives();

            r = ~r;
                        
            ISorted<char> drives = new TreeSet<char>();

            for (char drive = 'A'; drive <= 'Z'; ++drive, r >>= 1)
            {
                if ((r & 1) != 0)
                {
                    if (QueryDosDevice(drive.ToString() + ":").Length == 0)
                        drives.Add(drive);
                }
            }
            return drives;
        }

        /// <summary>
        /// Retreives a list of all existing MS-DOS device names. 
        /// </summary>
        /// <returns>A list of all existing MS-DOS device names.</returns>
        /// <remarks>
        /// <para>This is equivalent to calling <c>QueryDosDevice(null)</c></para>
        /// <para>See documentation on MSDN for the Windows QueryDosDevice() method for more information.</para></remarks>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted = true)]
        public static string[] QueryAllDosDevices()
        {
            return QueryDosDevice(null);
        }

        /// <summary>
        /// Retrieves information about MS-DOS device names. 
        /// The function can obtain the current mapping for a particular MS-DOS device name. 
        /// The function can also obtain a list of all existing MS-DOS device names.
        /// </summary>
        /// <param name="device">The device.</param>
        /// <returns>An MS-DOS device name string specifying the target of the query. The device name cannot have a 
        /// trailing backslash. This parameter can be <c>null</c>. In that case, the QueryDosDevice function 
        /// will return an array of all existing MS-DOS device names</returns>
        /// <remarks>See documentation on MSDN for the Windows QueryDosDevice() method for more information.</remarks>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
        public static string[] QueryDosDevice(string device)
        {
            uint returnSize = 0;
            int maxSize = 1024;

            while (returnSize == 0)
            {
                StringBuilder buffer = new StringBuilder(maxSize);

                returnSize = UnsafeNativeMethods.QueryDosDeviceW(device, buffer, (uint)buffer.Capacity);
                int lastError = Marshal.GetLastWin32Error();
                if (returnSize > 0)
                {
                    return buffer.ToString().Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
                }
                else if (lastError == Win32Errors.ERROR_INSUFFICIENT_BUFFER)
                {
                    maxSize *= 2;
                }
                else
                {
                    Marshal.ThrowExceptionForHR(lastError);
                    return new string[0];
                }
            }
            // Actually we never get to here
            return new string[0];
        }

        /// <summary>
        /// Determines whether the specified volume name is a defined volume on the current computer.
        /// </summary>
        /// <param name="volume">A string representing the path to a volume (eg. "C:\", "D:", "P:\Mountpoint\Backup", "\\?\Volume{c0580d5e-2ad6-11dc-9924-806e6f6e6963}\")</param>
        /// <returns>
        /// 	<c>true</c> if the specified volume is a defined volume; otherwise, <c>false</c>.
        /// </returns>
        /// <remarks>The trailing backslash is optional</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="volume"/> is a <c>null</c> reference</exception>
        /// <exception cref="Win32Exception">Upon error retreiving the volume name</exception>
        public static bool IsVolume(string volume)
        {
            if (volume == null)
                throw new ArgumentNullException("volume");

            if (volume.Length == 0)
                return false;

            string volumePath = PathUtils.AppendBackslash(volume);
            StringBuilder volumeNameBuilder = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumeNameForVolumeMountPointW(volumePath, volumeNameBuilder, (uint)volumeNameBuilder.Capacity))
            {
                return false;
            }
            return true;
        }

        /// <summary>
        /// Retrieves the unique volume name for the specified volume mount point or root directory.
        /// </summary>
        /// <param name="mountPoint">The path of a volume mount point (with or without a trailing backslash, "\") or a drive letter indicating a root directory (eg. "C:" or "D:\").</param>
        /// <returns>The unique volume name of the form "\\?\Volume{GUID}\" where GUID is the GUID that identifies the volume.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="mountPoint"/> is a <c>null</c> reference</exception>
        /// <exception cref="ArgumentException"><paramref name="mountPoint"/> is an empty string</exception>        
        /// <exception cref="Win32Exception">Upon error retreiving the volume name</exception>
        /// <remarks>See the MSDN documentation on the method GetVolumeNameForVolumeMountPoint() for more information.</remarks>
        public static string GetUniqueVolumeNameForVolumeMountPoint(string mountPoint)
        {
            if (mountPoint == null)
                throw new ArgumentNullException("mountPoint");

            if (mountPoint.Length == 0)
                throw new ArgumentException(Plossum.Resources.Win32Strings.MountPointMustBeNonEmpty);

            mountPoint = PathUtils.AppendBackslash(mountPoint);

            // Get the volume name alias. This may be different from the unique volume name in some
            // rare cases.
            StringBuilder volumeName = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumeNameForVolumeMountPointW(mountPoint, volumeName, (uint)volumeName.Capacity))
                throw new Win32Exception();

            // Get the unique volume name
            StringBuilder uniqueVolumeName = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumeNameForVolumeMountPointW(volumeName.ToString(), uniqueVolumeName, (uint)volumeName.Capacity))
                throw new Win32Exception();

            return uniqueVolumeName.ToString();
        }

        /// <summary>
        /// Retrieves the unique name of the volume mount point where the specified path is mounted.
        /// </summary>
        /// <param name="path">A pointer to the input path string. Both absolute and relative file and 
        /// directory names, for example ".", are acceptable in this path.
        /// If you specify a relative directory or file name without a volume qualifier, 
        /// GetUniqueVolumeNameForPath returns the drive letter of the boot volume.</param>
        /// <returns>The unique name of the volume mount point where the specified path is mounted</returns>
        /// <remarks>See the MSDN documentation on the method GetVolumePathName() for more information.</remarks>
        /// <exception cref="ArgumentNullException"><paramref name="path"/> is a <c>null</c> reference</exception>
        /// <exception cref="ArgumentException"><paramref name="path"/> is an empty string</exception>        
        /// <exception cref="Win32Exception">Upon error retreiving the volume name</exception>
        public static string GetUniqueVolumeNameForPath(string path)
        {
            if (path == null)
                throw new ArgumentNullException("path");

            if (path.Length == 0)
                throw new ArgumentException(Plossum.Resources.Win32Strings.MountPointMustBeNonEmpty);

            path = PathUtils.AppendBackslash(path);

            // Get the root path of the volume
            StringBuilder volumeRootPath = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumePathNameW(path, volumeRootPath, (uint)volumeRootPath.Capacity))
                throw new Win32Exception();

            // Get the volume name alias (might be different from the unique volume name in rare cases)
            StringBuilder volumeName = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumeNameForVolumeMountPointW(volumeRootPath.ToString(), volumeName, (uint)volumeName.Capacity))
                throw new Win32Exception();

            // Gte the unique volume name
            StringBuilder uniqueVolumeName = new StringBuilder(UnsafeNativeMethods.MAX_PATH);
            if (!UnsafeNativeMethods.GetVolumeNameForVolumeMountPointW(volumeName.ToString(), uniqueVolumeName, (uint)uniqueVolumeName.Capacity))
                throw new Win32Exception();

            return uniqueVolumeName.ToString();
        }

        /// <summary>
        ///  Retreives the Win32 device name from the volume name
        /// </summary>
        /// <param name="volumeName">Name of the volume.</param>
        /// <returns>The Win32 device name from the volume name</returns>
        [SecurityPermission(SecurityAction.LinkDemand, Unrestricted=true)]
        public static string GetDeviceForVolumeName(string volumeName)
        {
            if (volumeName == null)
                throw new ArgumentNullException("volumeName");

            if (volumeName.Length == 0)
                throw new ArgumentException(Plossum.Resources.Win32Strings.VolumeNameMustBeNonEmpty);

            volumeName = PathUtils.RemoveTrailingBackslash(volumeName);
            
            // Eliminate the GLOBALROOT prefix if present
            const string globalRootPrefix = @"\\?\GLOBALROOT";

            if (volumeName.StartsWith(globalRootPrefix, StringComparison.OrdinalIgnoreCase))
                return volumeName.Substring(globalRootPrefix.Length);

            // If this is a volume name, get the device
            const string dosPrefix = @"\\?\";
            const string volumePrefix = @"\\?\Volume";

            if (volumeName.StartsWith(volumePrefix, StringComparison.OrdinalIgnoreCase))
            {
                // Isolate the DOS device for the volume name (in the format Volume{GUID})
                string dosDevice = volumeName.Substring(dosPrefix.Length);

                // Get the real device underneath
                return QueryDosDevice(dosDevice)[0];
            }

            return volumeName;
        }

        /// <summary>
        /// Retrieves a list of path names for the specified volume name.
        /// </summary>
        /// <param name="volumeName">The volume name.</param>
        /// <returns>An array containing the path names for the specified volume.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="volumeName"/> is a <c>null</c> reference</exception>
        /// <exception cref="T:System.IO.FileNotFoundException">The volume name specified was invalid, did not exist or was not ready.</exception>
        /// <remarks>For more information about this method see the MSDN documentation on GetVolumePathNamesForVolumeName().</remarks>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static string[] GetVolumePathNamesForVolume(string volumeName)
        {
            if (volumeName == null)
                throw new ArgumentNullException("volumeName");

            uint requiredLength = 0;
            char[] buffer = new char[UnsafeNativeMethods.MAX_PATH];

            if (!UnsafeNativeMethods.GetVolumePathNamesForVolumeNameW(volumeName, buffer, (uint)buffer.Length, ref requiredLength))
            {
                // Not enough room in buffer perhaps? Try a bigger one
                buffer = new char[requiredLength];
                if (!UnsafeNativeMethods.GetVolumePathNamesForVolumeNameW(volumeName, buffer, (uint)buffer.Length, ref requiredLength))
                    Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            ArrayList<string> displayNames = new ArrayList<string>();
            StringBuilder displayName = new StringBuilder();

            for (int i = 0; i < requiredLength; i++)
            {
                if (buffer[i] == '\0')
                {
                    if (displayName.Length > 0)
                        displayNames.Add(displayName.ToString());
                    displayName.Length = 0;
                }
                else
                {
                    displayName.Append(buffer[i]);
                }
            }

            return displayNames.ToArray();
        }

        /// <summary>
        /// Gets the shortest display name for the specified <paramref name="volume"/>.
        /// </summary>
        /// <param name="volumeName">The volume name.</param>
        /// <returns>The shortest display name for the specified volume found, or <c>null</c> if no display names were found.</returns>
        /// <exception cref="ArgumentNullException"><paramref name="volumeName"/> is a <c>null</c> reference</exception>
        /// <exception cref="Win32Exception">An error occured during a system call, such as the volume name specified was invalid or did not exist.</exception>
        /// <remarks>This method basically returns the shortest string returned by <see cref="GetVolumePathNamesForVolume"/></remarks>        
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static string GetDisplayNameForVolume(string volumeName)
        {
            string[] volumeMountPoints = GetVolumePathNamesForVolume(volumeName);

            if (volumeMountPoints.Length == 0)
                return null;

            string smallestMountPoint = volumeMountPoints[0];
            for (int i = 1; i < volumeMountPoints.Length; i++)
            {
                if (volumeMountPoints[i].Length < smallestMountPoint.Length)
                    smallestMountPoint = volumeMountPoints[i];
            }
            return smallestMountPoint;
        }

        /// <summary>
        /// Retrieves information about the file system and volume associated with the specified root directory.
        /// </summary>
        /// <param name="rootPathName">The root directory of the volume to be described.</param>
        /// <returns>A <see cref="VolumeInfo"/> instance describing the volume associatied with the specified root directory</returns>
        /// <exception cref="System.IO.FileNotFoundException"><paramref name="rootPathName"/> was not a valid volume name</exception>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static VolumeInfo GetVolumeInformation(string rootPathName)
        {
            StringBuilder volumeNameBuffer = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            StringBuilder fileSystemNameBuffer = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            uint serialNumber = 0;
            uint maximumComponentLength = 0;
            uint fileSystemFlags = 0;

            if (!UnsafeNativeMethods.GetVolumeInformationW(rootPathName, volumeNameBuffer, volumeNameBuffer.Capacity,
                out serialNumber, out maximumComponentLength, out fileSystemFlags, fileSystemNameBuffer,
                fileSystemNameBuffer.Capacity))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            return new VolumeInfo(volumeNameBuffer.ToString(), fileSystemFlags, serialNumber,
                (int)maximumComponentLength, fileSystemNameBuffer.ToString());
        }

        /// <summary>
        /// Retrieves information about the file system and volume associated with the specified <see cref="System.IO.FileStream"/>.
        /// </summary>
        /// <param name="file">The file.</param>
        /// <returns>A <see cref="VolumeInfo"/> instance describing the volume associatied with the specified root directory</returns>
        /// <exception cref="System.IO.FileNotFoundException"><paramref name="rootPathName"/> was not a valid volume name</exception>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static VolumeInfo GetVolumeInformation(FileStream file)
        {
            if (file == null)
                throw new ArgumentNullException("file");
            StringBuilder volumeNameBuffer = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            StringBuilder fileSystemNameBuffer = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            uint serialNumber = 0;
            uint maximumComponentLength = 0;
            uint fileSystemFlags = 0;

            if (!UnsafeNativeMethods.GetVolumeInformationByHandleW(file.SafeFileHandle.DangerousGetHandle(), volumeNameBuffer, volumeNameBuffer.Capacity,
                out serialNumber, out maximumComponentLength, out fileSystemFlags, fileSystemNameBuffer,
                fileSystemNameBuffer.Capacity))
            {
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
            }

            return new VolumeInfo(volumeNameBuffer.ToString(), fileSystemFlags, serialNumber,
                (int)maximumComponentLength, fileSystemNameBuffer.ToString());            
        }

        /// <summary>
        /// Sets the label of a file system volume.
        /// </summary>
        /// <param name="rootPathName">The root directory of a file system volume. This is the volume the function will label.</param>
        /// <param name="volumeName">A name for the volume. If this parameter is a <c>null</c> reference, the method deletes 
        /// the label from the specified volume.</param>
        /// <exception cref="ArgumentNullException"><paramref name="rootPathName"/> is a <c>null</c> reference.</exception>        
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static void SetVolumeLabel(string rootPathName, string volumeName)
        {
            if (rootPathName == null)
                throw new ArgumentNullException("rootPathName");

            rootPathName = PathUtils.AppendBackslash(rootPathName);

            if (!UnsafeNativeMethods.SetVolumeLabelW(rootPathName, volumeName))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        /// <summary>
        /// Mounts the specified volume at the specified volume mount point.
        /// </summary>
        /// <param name="volumeMountPoint">The volume mount point where the volume is to be mounted. This may be a root directory (X:\) or a directory on a volume (X:\mnt\).</param>
        /// <param name="volumeName">The volume to be mounted. This string must be a unique volume name of the form "\\?\Volume{GUID}\" where GUID is a GUID that identifies the volume. The \\?\ turns off path parsing and is ignored as part of the path. For example, "\\?\C:\myworld\private" is seen as "C:\myworld\private".</param>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static void SetVolumeMountPoint(string volumeMountPoint, string volumeName)
        {
            if (volumeMountPoint == null)
                throw new ArgumentNullException("volumeMountPoint");

            if (volumeName == null)
                throw new ArgumentNullException("volumeName");

            if (!UnsafeNativeMethods.SetVolumeMountPointW(volumeMountPoint, volumeName))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        /// <summary>
        /// Unmounts the volume from the specified volume mount point.
        /// </summary>
        /// <param name="volumeMountPoint">The volume mount point to be unmounted. This may be a root directory (X:\, in which case the DOS drive letter assignment is removed) or a directory on a volume (X:\mnt\).</param>
        /// <exception cref="ArgumentNullException"><paramref name="volumeMountPoint"/> is <c>null</c></exception>
        /// <remarks>It is not an error to attempt to unmount a volume from a volume mount point when there is no volume actually mounted at that volume mount point.</remarks>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static void DeleteVolumeMountPoint(string volumeMountPoint)
        {
            if (volumeMountPoint == null)
                throw new ArgumentNullException("volumeMountPoint");

            volumeMountPoint = PathUtils.AppendBackslash(volumeMountPoint);

            if (!UnsafeNativeMethods.DeleteVolumeMountPointW(volumeMountPoint))
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());
        }

        /// <summary>
        /// Retrieves the name of all volumes on the computer. 
        /// </summary>
        /// <returns>An array containing the unique names of all volumes found.</returns>
        /// <remarks>You should not assume any correlation between the order of volumes returned with these functions and the order of volumes on the computer. In particular, do not assume any correlation between volume order and drive letters as assigned by the BIOS (if any) or the Disk Administrator.</remarks>
        /// <exception cref="Win32Exception">Upon error</exception>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static string[] GetVolumes()
        {
            StringBuilder volumeNameBuilder = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            ArrayList<string> volumes = new ArrayList<string>();

            IntPtr handle = UnsafeNativeMethods.FindFirstVolumeW(volumeNameBuilder, volumeNameBuilder.Capacity);
            if (handle == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
                Marshal.ThrowExceptionForHR(Marshal.GetHRForLastWin32Error());

            volumes.Add(volumeNameBuilder.ToString());
            volumeNameBuilder.Length = 0;

            while (UnsafeNativeMethods.FindNextVolumeW(handle, volumeNameBuilder, volumeNameBuilder.Capacity))
            {
                volumes.Add(volumeNameBuilder.ToString());
                volumeNameBuilder.Length = 0;
            }
            
            int lastError = Marshal.GetLastWin32Error();

            if (lastError != Win32Errors.ERROR_NO_MORE_FILES)
                throw new Win32Exception(lastError);

            if (!UnsafeNativeMethods.FindVolumeClose(handle))
                throw new Win32Exception();

            return volumes.ToArray();
        }

        /// <summary>
        /// Gets the names of the mount points on the specified volume.
        /// </summary>
        /// <param name="rootPathName">The unique volume name of the volume to scan for volume mount points.</param>
        /// <returns>The names of the mount points on the specified volume.</returns>
        /// <exception cref="DeviceNotReadyException">The device represented by the specified volume is not ready</exception>
        /// <exception cref="Win32Exception">Another error occured</exception>
        [SecurityPermission(SecurityAction.LinkDemand)]
        public static string[] GetVolumeMountPoints(string rootPathName)
        {
            if (rootPathName == null)
                throw new ArgumentNullException("rootPathName");

            rootPathName = PathUtils.AppendBackslash(rootPathName);

            StringBuilder volumeMountPointNameBuilder = new StringBuilder(UnsafeNativeMethods.MAX_PATH + 1);
            ArrayList<string> mountPoints = new ArrayList<string>();

            try
            {
                IntPtr handle = UnsafeNativeMethods.FindFirstVolumeMountPointW(rootPathName, volumeMountPointNameBuilder,
                    volumeMountPointNameBuilder.Capacity);


                int lastError = Marshal.GetLastWin32Error();

                if (handle == UnsafeNativeMethods.INVALID_HANDLE_VALUE)
                {
                    if (lastError != Win32Errors.ERROR_NO_MORE_FILES)
                        throw new Win32Exception(lastError);
                    else
                        return new string[0];
                }

                mountPoints.Add(volumeMountPointNameBuilder.ToString());
                volumeMountPointNameBuilder.Length = 0;

                while (UnsafeNativeMethods.FindNextVolumeMountPointW(handle, volumeMountPointNameBuilder,
                    volumeMountPointNameBuilder.Capacity))
                {
                    mountPoints.Add(volumeMountPointNameBuilder.ToString());
                    volumeMountPointNameBuilder.Length = 0;
                }

                lastError = Marshal.GetLastWin32Error();
                if (lastError != Win32Errors.ERROR_NO_MORE_FILES)
                    throw new Win32Exception(lastError);

                if (!UnsafeNativeMethods.FindVolumeMountPointClose(handle))
                    throw new Win32Exception();

            }
            catch (Win32Exception w32e)
            {
                if (w32e.NativeErrorCode == Win32Errors.ERROR_NOT_READY)
                    throw new DeviceNotReadyException("The device is not ready", w32e);

                throw;
            }

            return mountPoints.ToArray();
        }

        /// <summary>
        /// Determines whether a disk drive is a removable, fixed, CD-ROM, RAM disk, or network drive.
        /// </summary>
        /// <param name="rootPathName">The root directory for the drive.</param>
        /// <returns>The type of the drive.</returns>
        public static DriveTypes GetDriveType(string rootPathName)
        {
            return (DriveTypes)UnsafeNativeMethods.GetDriveType(rootPathName);
        }
    }
}
